using System;
using FamilyTreeProject.GEDCOM.Records;
using FamilyTreeProject.GEDCOM.IO;
using System.IO;
using FamilyTreeProject.GEDCOM.Common;
using System.Text;
using FamilyTreeProject.Common;

namespace FamilyTreeProject.GEDCOM
{
    /// <summary>
    /// This Class provides utilities for working with GEDCOM 5.5 data
    /// </summary>
    public class GEDCOMDocument
    {
        #region Private Members

        private GEDCOMRecordList records = new GEDCOMRecordList();
        private GEDCOMHeaderRecord headerRecord;
        private GEDCOMRecordList familyRecords;
        private GEDCOMRecordList individualRecords;
        private GEDCOMRecordList multimediaRecords;
        private GEDCOMRecordList noteRecords;
        private GEDCOMRecordList repositoryRecords;
        private GEDCOMRecordList sourceRecords;
        private GEDCOMRecordList submitterRecords;
        private GEDCOMRecord trailerRecord;

        #endregion

        #region Public Properties

        public GEDCOMRecordList FamilyRecords
        {
            get
            {
                if (familyRecords == null)
                    familyRecords = records.GetLinesByTag(GEDCOMTag.FAM);
                return familyRecords;
            }
        }

        public GEDCOMRecordList IndividualRecords
        {
            get
            {
                if (individualRecords == null)
                    individualRecords = records.GetLinesByTag(GEDCOMTag.INDI);
                return individualRecords;
            }
        }

        public GEDCOMRecordList MultimediaRecords
        {
            get
            {
                if (multimediaRecords == null)
                    multimediaRecords = records.GetLinesByTag(GEDCOMTag.OBJE);
                return multimediaRecords;
            }
        }

        public GEDCOMRecordList NoteRecords
        {
            get
            {
                if (noteRecords == null)
                    noteRecords = records.GetLinesByTag(GEDCOMTag.NOTE);
                return noteRecords;
            }
        }

        public GEDCOMRecordList Records
        {
            get
            {
                return records;
            }
        }

        public GEDCOMRecordList RepositoryRecords
        {
            get
            {
                if (repositoryRecords == null)
                    repositoryRecords = records.GetLinesByTag(GEDCOMTag.REPO);
                return repositoryRecords;
            }
        }

        public GEDCOMRecordList SourceRecords
        {
            get
            {
                if (sourceRecords == null)
                    sourceRecords = records.GetLinesByTag(GEDCOMTag.SOUR);
                return sourceRecords;
            }
        }

        public GEDCOMSubmissionRecord Submission
        {
            get { return records.GetLineByTag<GEDCOMSubmissionRecord>(GEDCOMTag.SUBN); }
        }

        public GEDCOMRecordList SubmitterRecords
        {
            get
            {
                if (submitterRecords == null)
                    submitterRecords = records.GetLinesByTag(GEDCOMTag.SUBM);
                return submitterRecords;
            }
        }

        #endregion

        #region Public Methods

        #region AddRecord

        /// <summary>
        /// Adds a record to the GEDCOM Document
        /// </summary>
        /// <param name="record">The record to add</param>
        public void AddRecord(GEDCOMRecord record)
        {
            Requires.ArgumentNotNull<GEDCOMRecord>(record);

            records.Add(record);

            //clear the assoicated RecordList so it is refreshed next time around
            ClearList(record.TagName);
        }

        #endregion

        #region AddRecords

        /// <summary>
        /// Adds a List of records to the GEDCOM Document
        /// </summary>
        /// <param name="records">The list of records to add</param>
        public void AddRecords(GEDCOMRecordList records)
        {
            Requires.ArgumentNotNull<GEDCOMRecordList>(records);
            
            this.records.AddRange(records);
        }

        #endregion

        #region Load Methods

        /// <summary>
        /// Loads the GEDCOM Document from a file.
        /// </summary>
        /// <param name="fileName">The file name of the file</param>
        public void Load(string fileName)
        {
            Load(GEDCOMReader.Create(new FileStream(fileName, FileMode.Open, FileAccess.Read)));
        }

        /// <summary>
        /// Loads the GEDCOM Document from a Stream
        /// </summary>
        /// <param name="stream">The stream to load</param>
        public void Load(Stream stream)
        {
            Load(GEDCOMReader.Create(stream));
        }

        /// <summary>
        /// Loads the GEDCOM Document from a TextReader
        /// </summary>
        /// <param name="reader">The TextReader to load</param>
        public void Load(TextReader reader)
        {
            Load(GEDCOMReader.Create(reader));
        }

        /// <summary>
        /// Loads the GEDCOM Document from a GEDCOMReader
        /// </summary>
        /// <param name="reader">The GEDCOMReader to load.</param>
        public void Load(GEDCOMReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            //Read the GEDCOM file into a GEDCOMRecords Collection
            records = reader.Read();
            reader.Close();
        }

        #endregion

        #region LoadGEDCOM Methods

        /// <summary>
        /// Loads the GEDCOM Document from a String
        /// </summary>
        /// <param name="text">The String to load</param>
        public void LoadGEDCOM(string text)
        {
            Load(GEDCOMReader.Create(text));
        }

        #endregion

        #region RemoveRecord

        /// <summary>
        /// Removes a single record from the Document
        /// </summary>
        /// <param name="record"></param>
        public void RemoveRecord(GEDCOMRecord record)
        {
            Requires.ArgumentNotNull<GEDCOMRecord>(record);

            if (records.Remove(record))
                //clear the assoicated RecordList so it is refreshed next time around
                ClearList(record.TagName);
            else
                throw new ArgumentOutOfRangeException();
        }

        #endregion

        #region Save Methods

        /// <summary>
        /// Save the GEDCOM Document to a file.
        /// </summary>
        /// <param name="fileName">The file name of the file.</param>
        public void Save(string fileName)
        {
            //Make sure file does not exist
            if (File.Exists(fileName))
                File.Delete(fileName);
            Save(GEDCOMWriter.Create(new FileStream(fileName, FileMode.CreateNew, FileAccess.Write)));
        }

        /// <summary>
        /// Save the GEDCOM Document to a Stream.
        /// </summary>
        /// <param name="stream">The streanm to save to.</param>
        public void Save(Stream stream)
        {
            Save(GEDCOMWriter.Create(stream));
        }

        /// <summary>
        /// Save the GEDCOM Document to a TextWriter
        /// </summary>
        /// <param name="writer">The TextWriter to save to</param>
        public void Save(TextWriter writer)
        {
            Save(GEDCOMWriter.Create(writer));
        }

        /// <summary>
        /// Save the GEDCOM Document to a GEDCOMWriter
        /// </summary>
        /// <param name="writer">The GEDCOMWriter to save to.</param>
        public void Save(GEDCOMWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            if (SelectTrailer() == null)
                AddRecord(new GEDCOMRecord(0, "", "", "TRLR", ""));

            //Write Header
            writer.WriteRecord(SelectHeader());
            
            //Write individuals
            writer.WriteRecords(IndividualRecords, true);

            //Write Trailer
            writer.WriteRecord(SelectTrailer());

            writer.Close();
        }

        #endregion

        #region SaveGEDCOM Methods

        /// <summary>
        /// Save the GEDCOM Document to a String
        /// </summary>
        /// <returns>The String representation of the document</returns>
        public string SaveGEDCOM()
        {
            StringBuilder sb = new StringBuilder();

            GEDCOMWriter writer = GEDCOMWriter.Create(sb);
            writer.NewLine = "\n";
            Save(writer);

            return sb.ToString();
        }

        #endregion

        #region Select Methods

        public GEDCOMFamilyRecord SelectFamilyRecord(string Id)
        {
            return FamilyRecords[Id] as GEDCOMFamilyRecord;
        }

        public GEDCOMHeaderRecord SelectHeader()
        {
            if (headerRecord == null)
                headerRecord = records.GetLineByTag<GEDCOMHeaderRecord>(GEDCOMTag.HEAD);
            return headerRecord;
        }

        public GEDCOMIndividualRecord SelectIndividualRecord(string Id)
        {
            GEDCOMIndividualRecord individual;
            try
            {
                individual = IndividualRecords[Id] as GEDCOMIndividualRecord;
            }
            catch (ArgumentOutOfRangeException exc)
            {
                individual = null;
            }
            return individual;
        }

        public GEDCOMMultimediaRecord SelectMultimediaRecord(string Id)
        {
            return MultimediaRecords[Id] as GEDCOMMultimediaRecord;
        }

        public GEDCOMNoteRecord SelectNoteRecord(string Id)
        {
            return NoteRecords[Id] as GEDCOMNoteRecord;
        }

        public GEDCOMRecord SelectRecord(string Id)
        {
            GEDCOMRecord record;
            try
            {
                record = records[Id];
            }
            catch (ArgumentOutOfRangeException exc)
            {
                record = null;
            }
            return record;
        }

        public GEDCOMRepositoryRecord SelectRepositoryRecord(string Id)
        {
            return RepositoryRecords[Id] as GEDCOMRepositoryRecord;
        }

        public GEDCOMSourceRecord SelectSourceRecord(string Id)
        {
            return SourceRecords[Id] as GEDCOMSourceRecord;
        }

        public GEDCOMSubmitterRecord SelectSubmitterRecord(string Id)
        {
            return SubmitterRecords[Id] as GEDCOMSubmitterRecord;
        }

        public GEDCOMRecord SelectTrailer()
        {
            if (trailerRecord == null)
                trailerRecord = records.GetLineByTag<GEDCOMRecord>(GEDCOMTag.TRLR);
            return trailerRecord;
        }

        #endregion

        #region ToString Methods

        /// <summary>
        /// ToString creates a string represenattion of the GEDCOMDocument
        /// </summary>
        /// <returns>a String</returns>
        public override string ToString()
        {
            return records.ToString();
        }

        #endregion

        #endregion

        private void ClearList(GEDCOMTag tag)
        {
            switch (tag)
            {
                case GEDCOMTag.INDI:
                    individualRecords = null;
                    break;
                default:
                    break;
            }
        }
    }
}
